<?php

/**
 * @author		Ildar N. Shaimordanov <ildar-sh@mail.ru>
 * @link		http://debugger.ru/articles/thumbnails
 * @license		http://www.php.net/license/3_0.txt
 *				The PHP License, version 3.0
 */
class CCoreMediaThumbnail
{
	// <editor-fold defaultstate="collapsed" desc="constants">
	/**
	 * Maximal scaling
	 */
	const METHOD_SCALE_MAX	= 0;

	/**
	 * Minimal scaling
	 */
	const METHOD_SCALE_MIN	= 1;

	/**
	 * Cropping of fragment
	 */
	const METHOD_CROP		= 2;

	/**
	 * Align constants
	 */
	const ALIGN_CENTER		= 0;
	const ALIGN_LEFT		= -1;
	const ALIGN_RIGHT		= +1;
	const ALIGN_TOP			= -1;
	const ALIGN_BOTTOM		= +1;
	// </editor-fold>

	// <editor-fold defaultstate="collapsed" desc="construct (private)">
	private function __construct() {}
	// </editor-fold>

	// <editor-fold defaultstate="collapsed" desc="public functions">
	/**
	 * Display rendered image (send it to browser or to file).
	 * This method is a common implementation to render and output an image.
	 * The method calls the render() method automatically and outputs the
	 * image to the browser or to the file.
	 *
	 * @param	mixed	$mInput
	 * @param	mixed	$mOutput = null
	 * @param	array	$aOptions = array()
	 * @return	bool
	 */
	public static function Output($mInput, $mOutput = null, $aOptions = array())
	{
		// load source file and render image
		$rRenderImage = self::Render($mInput, $aOptions);
		if (!$rRenderImage)
		{
			return false;
		}

		// set output image type by default PNG image
		$iType = isset($aOptions['type']) ? $aOptions['type'] : IMAGETYPE_PNG;
		// before output to browsers send appropriate headers
		if (empty($mOutput))
		{
			$sContentType = image_type_to_mime_type($iType);
			if (!@headers_sent())
			{
				@header('Content-Type: '.$sContentType);
			}
			else
			{
				return false;
			}
		}

		// define outputing function
		$bResult = false;
		switch ($iType)
		{
			case IMAGETYPE_PNG:
				$bResult = empty($mOutput) ? imagepng($rRenderImage) : imagepng($rRenderImage, $mOutput);
				break;
			case IMAGETYPE_JPEG:
				$bResult = empty($mOutput) ? imagejpeg($rRenderImage) : imagejpeg($rRenderImage, $mOutput);
				break;
			default:
				return $bResult;
		}

		// output image (to browser or to file)
		if (!$bResult)
		{
			return $bResult;
		}

		imagedestroy($rRenderImage);
		return true;
	}

	/**
	 * Draw thumbnail result to resource.
	 *
	 * @param       mixed   $mInput
	 * @param       array   $aOptions = array()
	 * @return      resource | bool
	 */
	public static function Render($mInput, $aOptions = array())
	{
		$iWidth = $iHeight = $iX = $iY = $iW = $iH = 0;

		// create the source image
		$rSourceImage = self::_imageCreate($mInput);
		if (!is_resource($rSourceImage))
		{
			return false;
		}

		$iSourceWidth  = imagesx($rSourceImage);
		$iSourceHeight = imagesy($rSourceImage);

		// set default options
		static $aDefOptions = array(
			'width'   => 150,
			'height'  => 150,
			'method'  => self::METHOD_SCALE_MAX,
			'percent' => 0,
			'halign'  => self::ALIGN_CENTER,
			'valign'  => self::ALIGN_CENTER
		);
		foreach ($aDefOptions as $mKey => $mValue)
		{
			if (!isset($aOptions[$mKey]))
			{
				$aOptions[$mKey] = $mValue;
			}
		}

		$iDefAlign = ($iSourceHeight > $iSourceWidth) ? self::ALIGN_TOP : self::ALIGN_CENTER;
		$aOptions['valign'] = $iDefAlign;

		// estimate a rectangular portion of the source image and a size of the target image
		if ($aOptions['method'] == self::METHOD_CROP)
		{
			$iW = ($aOptions['percent']) ? floor($aOptions['percent'] * $iSourceWidth) : $aOptions['width'];
			$iH = ($aOptions['percent']) ? floor($aOptions['percent'] * $iSourceHeight) : $aOptions['height'];

			$iWidth  = $iW;
			$iHeight = $iH;

			$iY = self::_coord($aOptions['valign'], $iSourceHeight, $iH);
			$iX = self::_coord($aOptions['halign'], $iSourceWidth,  $iW);
		}
		else
		{
			$iX = $iY = 0;

			$iW = $iSourceWidth;
			$iH = $iSourceHeight;

			if ($aOptions['percent'])
			{
				$iWidth = floor($aOptions['percent'] * $iW);
				$iHeight = floor($aOptions['percent'] * $iH);
			}
			else
			{
				$iWidth  = $aOptions['width'];
				$iHeight = $aOptions['height'];

				if ($aOptions['method'] == self::METHOD_SCALE_MIN)
				{
					$iWw = $iW / $iWidth;
					$iHh = $iH / $iHeight;

					if ( $iWw > $iHh )
					{
						$iW = floor($iWidth * $iHh);
						$iX = self::_coord($aOptions['halign'], $iSourceWidth, $iW);
					}
					else
					{
						$iH = floor($iHeight * $iWw);
						$iY = self::_coord($aOptions['valign'], $iSourceHeight, $iH);
					}
				}
				else
				{
					if ($iH > $iW)
					{
						$iWidth  = floor($iHeight / $iH * $iW);
					}
					else
					{
						$iHeight = floor($iWidth / $iW * $iH);
					}
				}
			}
		}

		// create the target image
		$rTargetImage = (function_exists('imagecreatetruecolor'))
				? imagecreatetruecolor($iWidth, $iHeight) : imagecreate($iWidth, $iHeight);

		if (!is_resource($rTargetImage))
		{
			return false;
		}

		// copy the source image to the target image
		$bResult = false;
		if ($aOptions['method'] == self::METHOD_CROP)
		{
			$bResult = imagecopy($rTargetImage, $rSourceImage, 0, 0, $iX, $iY, $iW, $iH);
		}
		else if (function_exists('imagecopyresampled'))
		{
			$bResult = imagecopyresampled($rTargetImage, $rSourceImage, 0, 0, $iX, $iY, $iWidth, $iHeight, $iW, $iH);
		}
		else
		{
			$bResult = imagecopyresized($rTargetImage, $rSourceImage, 0, 0, $iX, $iY, $iWidth, $iHeight, $iW, $iH);
		}

		if (!$bResult)
		{
			 return false;
		}

		imagedestroy($rSourceImage);

		return $rTargetImage;
	}
	// </editor-fold>

	// <editor-fold defaultstate="collapsed" desc="private functions">
	/**
	 * Create a GD image resource from given input.
	 *
	 * This method tried to detect what the input, if it is a file the
	 * CreateImageFromFile will be called, otherwise CreateImageFromString()
	 *
	 * @param	mixed	$mInput
	 * @return	resource | false
	 */
	private static function _imageCreate($mInput)
	{
		if (is_file($mInput))
		{
			return self::_imageCreateFromFile($mInput);
		}
		else if (is_string($mInput))
		{
			return self::_imageCreateFromString($mInput);
		}
		else
		{
			return $mInput;
		}
	}

	/**
	 * Create a GD image resource from file (JPEG, PNG support).
	 *
	 * @param	string	$sFileName
	 * @return	mixed | false
	 */
	private static function _imageCreateFromFile($sFileName)
	{
		if (!is_file($sFileName) || !is_readable($sFileName))
		{
			return false;
		}

		// determine image format
		list( , , $_type) = getimagesize($sFileName);

		switch ($_type)
		{
			case IMAGETYPE_JPEG:
				return imagecreatefromjpeg($sFileName);
				break;
			case IMAGETYPE_PNG:
				return imagecreatefrompng($sFileName);
				break;
		}

		return false;
	}

	/**
	 * Create a GD image resource from a string data.
	 *
	 * @param	string	$sString
	 * @return	mixed | false
	 */
	private static function _imageCreateFromString($sString)
	{
		if (!is_string($sString) || empty($sString))
		{
			return false;
		}
		return imagecreatefromstring($sString);
	}

	/**
	 * @param	int	$iAlign
	 * @param	int	$iParam
	 * @param	int	$iSrc
	 * @return	int
	 */
	private static function _coord($iAlign, $iParam, $iSrc)
	{
		$iResult = 0;
		if ($iAlign < self::ALIGN_CENTER)
		{
			$iResult = 0;
		}
		else if ($iAlign > self::ALIGN_CENTER)
		{
			$iResult = $iParam - $iSrc;
		}
		else
		{
			$iResult = ($iParam - $iSrc) >> 1;
		}

		return $iResult;
	}
	// </editor-fold>
}
